Fox's Git Mirrors
Commit 35a7f9b7d15b529c9ed5cf5b5b31ee8aadaa796c
Parents : c4a09d0
Author : Ivan <e318cbc04468bd574db2b4523dddd710>
Signature : T66BB85Valid, signed by author
Date : 2026-08-13T18:00:59-05:00
feat: various fixes and cleanup
Changes
4 files changed, 45 insertions(+), 10 deletions(-)
Diff
diff --git a/pkg/interfaces/hdlc_decoder.go b/pkg/interfaces/hdlc_decoder.go
index 55f80ff1..b7f6e705 100644
--- a/pkg/interfaces/hdlc_decoder.go
+++ b/pkg/interfaces/hdlc_decoder.go
@@ -4,7 +4,9 @@
package interfaces
// hdlcStreamDecoder incrementally parses HDLC-framed packets from a byte stream.
-// Payload bytes are unescaped during assembly, and onFrame receives the decoded body.
+// Payload bytes are unescaped during assembly. onFrame receives a view of the
+// assembler buffer that is reused after the callback returns. Callers that
+// retain the frame must copy it. Transport.HandlePacket already copies.
type hdlcStreamDecoder struct {
mtu int
minPayload int
@@ -82,9 +84,7 @@ func (d *hdlcStreamDecoder) feedByte(b byte) {
ok = false
}
if ok {
- frame := make([]byte, len(d.data))
- copy(frame, d.data)
- d.onFrame(frame)
+ d.onFrame(d.data)
}
}
d.data = d.data[:0]
diff --git a/pkg/rgosh/e2e_test.go b/pkg/rgosh/e2e_test.go
index cb812e6b..0a8b6586 100644
--- a/pkg/rgosh/e2e_test.go
+++ b/pkg/rgosh/e2e_test.go
@@ -238,8 +238,12 @@ func TestE2E_RgoshLongLived(t *testing.T) {
var stdoutMu sync.Mutex
var stdoutBuf bytes.Buffer
exitCh := make(chan int, 1)
+ listenerReady := make(chan struct{}, 1)
dest.SetLinkEstablishedCallback(func(lnk any) {
- l := lnk.(*link.Link)
+ l, ok := lnk.(*link.Link)
+ if !ok || l == nil {
+ return
+ }
ch := l.GetChannel()
_ = RegisterNative(ch)
sess := NewSession(Config{
@@ -257,6 +261,10 @@ func TestE2E_RgoshLongLived(t *testing.T) {
return true
})
l.SetLinkClosedCallback(func(*link.Link) { sess.Close() })
+ select {
+ case listenerReady <- struct{}{}:
+ default:
+ }
})
_ = dest.Announce(false, nil, nil)
time.Sleep(100 * time.Millisecond)
@@ -268,6 +276,13 @@ func TestE2E_RgoshLongLived(t *testing.T) {
t.Fatal(err)
}
defer l.Teardown()
+
+ select {
+ case <-listenerReady:
+ case <-time.After(5 * time.Second):
+ t.Fatal("listener session not ready")
+ }
+
ch := l.GetChannel()
_ = RegisterNative(ch)
sess := NewSession(Config{Listener: false}, ChannelSender{Ch: ch})
@@ -291,11 +306,16 @@ func TestE2E_RgoshLongLived(t *testing.T) {
t.Fatal(err)
}
deadline := time.After(10 * time.Second)
+ lastVers := time.Now()
for sess.State() == StateWaitVers {
select {
case <-deadline:
t.Fatal("version timeout")
case <-time.After(50 * time.Millisecond):
+ if sess.State() == StateWaitVers && time.Since(lastVers) >= 2*time.Second {
+ _ = sess.SendVersion()
+ lastVers = time.Now()
+ }
}
}
if err := sess.SendExec(ExecRequest{
@@ -309,8 +329,11 @@ func TestE2E_RgoshLongLived(t *testing.T) {
_ = sess.SendStream(StreamStdin, nil, true)
select {
case <-exitCh:
- case <-time.After(20 * time.Second):
- t.Fatal("exit timeout")
+ case <-time.After(45 * time.Second):
+ stdoutMu.Lock()
+ out := stdoutBuf.String()
+ stdoutMu.Unlock()
+ t.Fatalf("exit timeout stdout=%q state=%s", out, sess.State())
}
if time.Since(start) < 6*time.Second {
t.Fatalf("session ended too fast: %v", time.Since(start))
diff --git a/pkg/rgosh/session.go b/pkg/rgosh/session.go
index 20b0e1cb..35767aab 100644
--- a/pkg/rgosh/session.go
+++ b/pkg/rgosh/session.go
@@ -600,9 +600,7 @@ func (s *Session) pumpProcess(proc ProcessHandle) {
}
}
- if sender != nil {
- _ = sender.Send(&ExitMessage{Compat: compat, ReturnCode: code})
- }
+ sendWhenReady(sender, &ExitMessage{Compat: compat, ReturnCode: code}, 5*time.Second)
if d, ok := sender.(interface{ WaitTxIdle(time.Duration) bool }); ok {
_ = d.WaitTxIdle(5 * time.Second)
}
@@ -809,6 +807,20 @@ func (s *Session) MutateDefaultCmdAppend(arg string) {
s.cfg.DefaultCmd = append(s.cfg.DefaultCmd, arg)
}
+// sendWhenReady waits up to wait for a ready outlet, then sends. Exit uses
+// this so a full TX window cannot drop the final status.
+func sendWhenReady(sender Sender, msg Message, wait time.Duration) {
+ if sender == nil {
+ return
+ }
+ if rs, ok := sender.(interface{ WaitReady(context.Context) error }); ok && wait > 0 {
+ ctx, cancel := context.WithTimeout(context.Background(), wait)
+ _ = rs.WaitReady(ctx)
+ cancel()
+ }
+ _ = sender.Send(msg)
+}
+
// sendLocked sends while temporarily releasing s.mu so channel IO cannot
// deadlock against pumpProcess. Caller must hold s.mu. On return s.mu is held.
func (s *Session) sendLocked(msg Message) error {
diff --git a/reticulum-go.rsm b/reticulum-go.rsm
index e67c1b1a..b8fd9dec 100644
Binary files a/reticulum-go.rsm and b/reticulum-go.rsm differ
Served by rngit 1.5.2 - Generated in 0.04s